package pwd.allen.byteBuddy.methodIntercept;

import net.bytebuddy.implementation.bind.annotation.*;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;

/**
 * 在拦截器中使用bytebuddy支持的一些注解
 *
 * @Argument 绑定单个参数
 * @AllArguments 绑定所有参数的数组
 * @This 当前被拦截的、动态生成的那个对象，拦截不了静态方法
 * @Origin 表示被拦截的目标相关对象，可以是目标方法或者目标类，只有拦截实例方法或者静态方法是可用
 * @DefaultCall 调用默认方法而非super的方法
 * @SuperCall 用于调用父类版本的方法，byte buddy会额外生成一个实现Callable和Runnable的类来实现调用
 * @RuntimeType 可以用在返回值、参数上，提示ByteBuddy禁用严格的类型检查
 * @Super 当前被拦截的、动态生成的那个对象包装对象，该对象内部的target为@This指定的对象，内部方法都委托给target
 * @FieldValue 注入被拦截对象的一个字段的值
 *
 * 如果想调用原方法，要调用@SuperCall，不能用@This或者@Super（会再次被拦截然后循环调用）
 *
 * @author pwdan
 * @create 2023-06-28 17:10
 **/
public class MethodIntercept2 {

    /**
     * @param args
     * @param proxy
     * @param method
     * @param callable
     * @return
     * @throws Exception
     */
    public static int anotherAdd(@AllArguments Object[] args, @This Object proxy
            , @Origin Method method, @Origin Class<?> clazz, @SuperCall Callable callable) throws Exception {
        System.out.println("被anotherAdd拦截");
        System.out.println("入参数组:" + Arrays.toString(args));
        System.out.println("被拦截并且被bytebuddy增强后的对象：" + proxy);
        // 通过查看生成类的字节码可以看到，method会在静态加载的时候获取并缓存下来，所以调用的时候不影响效率
        System.out.println("目标方法：" + method.getName());
        System.out.println("目标类型：" + clazz.getName());
        System.out.println("直接调用目标方法，返回结果为：" + callable.call());
        return 100;
    }

    /**
     * 加上@RuntimeType后并不能强制委托给该方法，最终还是第一个方法即anotherAdd被委托
     *
     * 如果拦截的是静态方法，被委托的是antherAdd2；因为antherAdd用了@This，不支持静态方法，不会被委托
     *
     * 如果使用redefine则不会委托给这个方法，因为redefine原方法会消失，用不了@SuperCall
     * @return
     */
    @RuntimeType
    public static Object anotherAdd2(@AllArguments Object[] args
            , @Origin Method method, @Origin Class<?> clazz, @SuperCall Callable callable) throws Exception {
        System.out.println("被anotherAdd2拦截");
        System.out.println("入参数组:" + Arrays.toString(args));
        System.out.println("目标方法：" + method.getName());
        System.out.println("目标类型：" + clazz.getName());
        Object call = callable.call();
        System.out.println("直接调用目标方法，返回结果为：" + call);
        return call;
    }
}
